﻿namespace FastDynamicMemberAccessor
{
    using System;
    using System.Collections;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.Threading;

    internal abstract class MemberAccessor : IMemberAccessor
    {
        private IMemberAccessor _emittedMemberAccessor;
        protected readonly string _fieldName;
        protected readonly Type _targetType;
        private const string emmitedTypeName = "Member";
        protected static readonly Hashtable s_TypeHash = new Hashtable();

        static MemberAccessor()
        {
            s_TypeHash[typeof(sbyte)] = OpCodes.Ldind_I1;
            s_TypeHash[typeof(byte)] = OpCodes.Ldind_U1;
            s_TypeHash[typeof(char)] = OpCodes.Ldind_U2;
            s_TypeHash[typeof(short)] = OpCodes.Ldind_I2;
            s_TypeHash[typeof(ushort)] = OpCodes.Ldind_U2;
            s_TypeHash[typeof(int)] = OpCodes.Ldind_I4;
            s_TypeHash[typeof(uint)] = OpCodes.Ldind_U4;
            s_TypeHash[typeof(long)] = OpCodes.Ldind_I8;
            s_TypeHash[typeof(ulong)] = OpCodes.Ldind_I8;
            s_TypeHash[typeof(bool)] = OpCodes.Ldind_I1;
            s_TypeHash[typeof(double)] = OpCodes.Ldind_R8;
            s_TypeHash[typeof(float)] = OpCodes.Ldind_R4;
        }

        protected MemberAccessor(MemberInfo member)
        {
            this._targetType = member.ReflectedType;
            this._fieldName = member.Name;
        }

        protected abstract void _EmitGetter(TypeBuilder type);
        protected abstract void _EmitSetter(TypeBuilder type);
        private Assembly EmitAssembly()
        {
            AssemblyName name = new AssemblyName {
                Name = "PropertyAccessorAssembly"
            };
            AssemblyBuilder builder = Thread.GetDomain().DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
            TypeBuilder type = builder.DefineDynamicModule("Module").DefineType("Member", TypeAttributes.Sealed | TypeAttributes.Public);
            type.AddInterfaceImplementation(typeof(IMemberAccessor));
            type.DefineDefaultConstructor(MethodAttributes.Public);
            this._EmitGetter(type);
            this._EmitSetter(type);
            type.CreateType();
            return builder;
        }

        private void EnsureInit()
        {
            if (this._emittedMemberAccessor == null)
            {
                Assembly assembly = this.EmitAssembly();
                this._emittedMemberAccessor = assembly.CreateInstance("Member") as IMemberAccessor;
                if (this._emittedMemberAccessor == null)
                {
                    throw new Exception("Unable to create member accessor.");
                }
            }
        }

        public object Get(object target)
        {
            if (!this.CanRead)
            {
                throw new MemberAccessorException(string.Format("Member \"{0}\" does not have a get method.", this._fieldName));
            }
            this.EnsureInit();
            return this._emittedMemberAccessor.Get(target);
        }

        internal static MemberAccessor Make(PropertyInfo p_propertyInfo, FieldInfo p_fieldInfo)
        {
            if (p_propertyInfo != null)
            {
                return new PropertyAccessor(p_propertyInfo);
            }
            return new FieldAccessor(p_fieldInfo);
        }

        public void Set(object target, object value)
        {
            if (!this.CanWrite)
            {
                throw new MemberAccessorException(string.Format("Member \"{0}\" does not have a set method.", this._fieldName));
            }
            this.EnsureInit();
            this._emittedMemberAccessor.Set(target, value);
        }

        internal abstract bool CanRead { get; }

        internal abstract bool CanWrite { get; }

        internal abstract Type MemberType { get; }

        internal Type TargetType
        {
            get
            {
                return this._targetType;
            }
        }
    }
}

